package com.ebarlas.fractal.compile;

import java.util.Arrays;

import org.apache.bcel.Constants;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.InvokeInstruction;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.Type;

import com.ebarlas.fractal.util.Complex;

public class CompilerUtils {

    public static final String COMPLEX_CLASS_NAME = Complex.class.getName();
    
    public static final ObjectType COMPLEX_TYPE = new ObjectType(COMPLEX_CLASS_NAME);
    
    public static InvokeInstruction getComplexInvokeInst(InstructionFactory factory, String methodName, int args) {
        Type[] argTypes = new Type[args];
        Arrays.fill(argTypes, COMPLEX_TYPE);        
        return factory.createInvoke(COMPLEX_CLASS_NAME, methodName, COMPLEX_TYPE, argTypes, Constants.INVOKESTATIC); 
    }
    
    public static void pushComplexLiteral(ConstantPoolGen cp, InstructionList il, InstructionFactory factory, Complex value) {
        if(value.equals(Complex.ZERO)) {
            il.append(factory.createGetStatic(COMPLEX_CLASS_NAME, "ZERO", COMPLEX_TYPE));
        } else if(value.equals(Complex.ONE)) {
            il.append(factory.createGetStatic(COMPLEX_CLASS_NAME, "ONE", COMPLEX_TYPE));
        } else if(value.equals(Complex.TWO)) {
            il.append(factory.createGetStatic(COMPLEX_CLASS_NAME, "TWO", COMPLEX_TYPE));
        } else if(value.equals(Complex.THREE)) {
            il.append(factory.createGetStatic(COMPLEX_CLASS_NAME, "THREE", COMPLEX_TYPE));
        } else if(value.equals(Complex.I)) {
            il.append(factory.createGetStatic(COMPLEX_CLASS_NAME, "I", COMPLEX_TYPE));
        } else {
            // prepare new complex instance
            il.append(factory.createNew(CompilerUtils.COMPLEX_CLASS_NAME));
            il.append(InstructionConstants.DUP);          
            
            il.append(new PUSH(cp, value.getReal()));
            il.append(new PUSH(cp, value.getImaginary())); 
            
            // call constructor on prepared complex instance
            // now that we have compiled executes
            il.append(factory.createInvoke(
                    CompilerUtils.COMPLEX_CLASS_NAME, // class name
                    "<init>", // method name
                    Type.VOID, // return type
                    new Type[] { Type.DOUBLE, Type.DOUBLE }, // arg types
                    Constants.INVOKESPECIAL)); // type of invocation 
        }
    }
    
}
